<?php
/**
 * Created by PhpStorm.
 * Date: 2024/6/25
 * Time: 11:34
 */

namespace App\Handlers;

use Illuminate\Support\Facades\Log;

class Xfyun
{
    const model_map = [
        'Spark4.0-Ultra' => '4.0Ultra',
        'Spark-Max' => 'generalv3.5',
        'Spark-Pro' => 'generalv3',
        'Spark-V2.0' => 'generalv2',
        'Spark-Lite' => 'lite',
        'Spark-DeepSeek-R1' => 'xdeepseekr1',
        'Spark-DeepSeek-V3' => 'xdeepseekv3',
    ];

    /**
     * 对话
     * @param $content 输入内容
     * @param $ai_model 模型
     * @param $stream 是否流式
     * @return array|false
     * @throws \Exception
     */
    public function chat($content, $ai_model = 'lite', $stream = true)
    {
        $url = "https://spark-api-open.xf-yun.com/v1/chat/completions";
        $tmp = explode('_', $ai_model);
        $temperature = null;
        if (count($tmp) > 1) $temperature = array_pop($tmp);

        $ai_model_short = implode('_', $tmp);
        $model_real = self::model_map[$ai_model_short];
        $auth_token = config('services.spark_models_config')[$model_real];
        if (is_array($auth_token)) $auth_token = random_array($auth_token);

        /**
         * lite指向Lite版本;
         * generalv3指向Pro版本;
         * pro-128k指向Pro-128K版本;
         * generalv3.5指向Max版本;
         * max-32k指向Max-32K版本;
         * 4.0Ultra指向4.0 Ultra版本;
         */
        $data = [
            "model" =>  $model_real, # 指定请求的模型
            "messages" =>  [
                [
                    "role" =>  "user",
                    "content" =>  $content
                ]
            ],
            "tool_choice" => "auto",
            "top_k" => 4,
//            "temperature" => floatval($temperature),
            "max_tokens" => 2048,
            "stream" => $stream ? true : false,
        ];
        if ($temperature) $data['temperature'] = floatval($temperature);
        switch ($ai_model_short) {
            case 'Spark-DeepSeek-R1':
            case 'Spark-DeepSeek-V3':
                $url = 'http://maas-api.cn-huabei-1.xf-yun.com/v1/chat/completions';
                $data['max_tokens'] = 10240;
                $data['stream_options'] = ['include_usage' => true];
                unset($data['top_k']);
                break;
        }

        if ($stream) {
            $data['stream_options'] = ['include_usage' => true];
            $res_ori = $this->request($url, $data, $auth_token, [], 1, true);
        } else {
            $res_ori = $this->request($url, $data, $auth_token);
        }
        $res = json_decode($res_ori, true);
        if (! $res) return false;
        if ($res['error'] ??[]) {
            return [
                'code' => 0,
                'data' => [],
                'msg' => $res['error']['message'] ?? '',
            ];
        }
        if (!isset($res['choices'][0]['message']['content'])) {
            Log::debug('xfun-api: '.$res_ori);
            if ($res) throw new \Exception($res_ori);
            return false;
        }
        $answer = $res['choices'][0]['message']['content'] ?? null;
//        $output = vfu_array(explode('"""', $answer));
//        if (count($output) == 1) $answer = $output[0];
        $answer = strtr($answer, ["\r" => ""]);
        $answer = strtr($answer, ["\n\n" => "\n"]);
        $answer = trim($answer);

        $think = $res['choices'][0]['message']['reasoning_content'] ?? '';
        if ($think) {
            $think = strtr($think, ["\r" => ""]);
            $think = strtr($think, ["\n\n" => "\n"]);
            $think = trim($think);
        }
        return [
            'code' => 200,
            'data' => [
                'answer' => $answer,
                'questions' => [],
                'think' => $think ? [$think] : [],
            ],
            'msg' => '',
        ];
    }

    public function request($url, $params = [], $auth = '', $headers = [], $times = 3, $stream = false) {
        if (!$auth) $auth = config('services.xfun.api_password');
//        dump($auth);
        $now_time = 0;
        while (true) {
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);    // https请求时要设置为false 不验证证书和hosts  FALSE 禁止 cURL 验证对等证书（peer's certificate）, 自cURL 7.10开始默认为 TRUE。从 cURL 7.10开始默认绑定安装。
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);//检查服务器SSL证书中是否存在一个公用名(common name)。
            curl_setopt_array($curl, array(
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_ENCODING => '',
                CURLOPT_MAXREDIRS => 10,
                CURLOPT_TIMEOUT => 300,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                CURLOPT_CUSTOMREQUEST => 'POST',
                CURLOPT_POSTFIELDS => json_encode($params),
            ));

            $headers[] = 'Content-Type: application/json';
            $headers[] = 'Authorization: Bearer '.$auth;

            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

            // 流式则将结果收集起来
            if ($stream) {
                $stream_output = [
                    'id' => '',
                    'object' => '',
                    'created' => 0,
                    'chunks' => [],
                    'choices' => [
                        [
                            'index' => 0,
                            'message' => [
                                'role' => 'assistant',
                                'content' => '',
                                'reasoning_content' => '',
                            ],
                            'finish_reason' => 'stop'
                        ]
                    ],
                    'usage' => [
                        'prompt_tokens' => 0,
                        'completion_tokens' => 0,
                        'total_tokens' => 0,
                    ],
                ];
                $func = function ($ch, $data) use (&$stream_output) {
//                    dump($data);
                    $chunks = explode("\n", $data);
                    foreach ($chunks as $chunk) {
                        $chunk = trim($chunk);
                        if (!$chunk) continue;
                        $stream_output['chunks'][] = $chunk;
                        $res = preg_replace('/^data:/iu', '', $chunk);
                        if (! $res) continue ;
                        $res = json_decode($res, true);
                        if (! $res) continue;
                        if ($res['error'] ?? '') {
                            $stream_output = $res;
                            break;
                        } else {
                            if ($res['choices'][0]['delta']['reasoning_content'] ?? '') $stream_output['choices'][0]['message']['reasoning_content'] .= $res['choices'][0]['delta']['reasoning_content'];
                            if ($res['choices'][0]['delta']['content'] ?? '') $stream_output['choices'][0]['message']['content'] .= $res['choices'][0]['delta']['content'];

                            // 结束标记
                            if (($res['choices'][0]['finish_reason'] ?? '') == 'stop') {
                                $stream_output['id'] = $res['id'];
                                $stream_output['object'] = $res['object'];
                                $stream_output['created'] = $res['created'];
                                $stream_output['usage']['prompt_tokens'] = $res['usage']['prompt_tokens'];
                                $stream_output['usage']['completion_tokens'] = $res['usage']['completion_tokens'];
                                $stream_output['usage']['total_tokens'] = $res['usage']['total_tokens'];
                            }
                        }
                    }

                    return strlen($data);
                };
                curl_setopt($curl, CURLOPT_WRITEFUNCTION, $func);
            }

            $response = curl_exec($curl);
//            dd($response, $stream_output);
//            dump($response, curl_error($curl));

            if (! $response) {
                if (($now_time++) < $times) {
                    continue;
                } else {
                    if (($err_msg = curl_error($curl))) {
                        $err_code = curl_errno($curl);
                        $error = $err_msg.'_'.$err_code;
                        Log::debug('xfun-api: '.$error);
                        throw new \Exception($error);
                    }
                }
            }
            if ($stream) $response = json_encode($stream_output);

            curl_close($curl);
            return $response;
        }
    }
}
